﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using DiskInspector.Model;

namespace DiskInspector
{
    public partial class FullDiskSearchForm : Form
    {
        private readonly int sectorsPerCluster;

        private readonly int sectorsPrecedingPartition;

        private readonly long totalSectors;

        private CancellationTokenSource cts;

        private ConcurrentQueue<Cluster> queue;

        private readonly ConcurrentDictionary<int, long> ProgressStates = new ConcurrentDictionary<int, long>();

        private readonly Stopwatch stopwatch = new Stopwatch();

        private bool ReadingFinished { get; set; }

        public FullDiskSearchForm(int sectorsPerCluster, int sectorsPrecedingPartition, long totalSectors)
        {
            this.sectorsPerCluster = sectorsPerCluster;
            this.sectorsPrecedingPartition = sectorsPrecedingPartition;
            this.totalSectors = totalSectors;
            InitializeComponent();
        }

        private async void btn_Search_Click(object sender, EventArgs e)
        {
            tb_ResultIndex.Clear();
            lbl_Searching.Text = String.Empty;
            pb_Search.Value = 0;
            btn_Search.Enabled = false;
            btn_SequentialSearch.Enabled = false;
            stopwatch.Reset();

            try
            {
                var totalClusters = totalSectors / sectorsPerCluster;

                using (cts = new CancellationTokenSource())
                {
                    List<Task<long>> tasks = new List<Task<long>>();
                    const int taskCount = 1;

                    stopwatch.Start();

                    for (int i = 0; i < taskCount; i++)
                    {
                        int index = i;

                        int clusterOffset;
                        if (int.TryParse(tb_ClusterOffset.Text, out clusterOffset))
                        {
                            clusterOffset = Convert.ToInt32(tb_ClusterOffset.Text);
                        }
                        else
                            clusterOffset = 1;

                        var offset = (sectorsPrecedingPartition + (clusterOffset - 1) * sectorsPerCluster + (totalClusters - clusterOffset) / taskCount * i * sectorsPerCluster) * DiskManager.SectorSize;
                        var task = Task.Run(() => SearchAsync(index, offset, totalClusters / taskCount, cts.Token));
                        tasks.Add(task);
                    }

                    var updateProgressTask = UpdateProgress(totalClusters, cts.Token);
                    var updateRemainingTask = UpdateRemaining(totalClusters, cts.Token);
                    var updateThreadsProgress = UpdateThreadsProgress(cts.Token);

                    await Task.WhenAll(tasks);

                    cts.Cancel();
                    await updateProgressTask;
                    await updateRemainingTask;
                    await updateThreadsProgress;

                    var taskHavingResult = tasks.Where(t => t.IsCompleted).FirstOrDefault(t => t.Result != 0);
                    if (taskHavingResult != null)
                    {
                        var clusterIndex = (taskHavingResult.Result / DiskManager.SectorSize - sectorsPrecedingPartition) / sectorsPerCluster;
                        tb_ResultIndex.Text = clusterIndex.ToString();
                        MessageBox.Show(@"查找已完成，发现结果", @"提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }
            }
            finally
            {
                btn_Search.Enabled = true;
                btn_SequentialSearch.Enabled = true;
                stopwatch.Stop();
                cts = null;
                ProgressStates.Clear();
            }
        }

        private async Task<long> SearchAsync(int index, long offset, long totalClusters, CancellationToken cancellationToken)
        {
            try
            {
                Console.WriteLine(@"{0}号线程启动，线程ID:{1}", index, Thread.CurrentThread.ManagedThreadId);

                ProgressStates[index] = 0;

                using (var disk = GetStream())
                {
                    var searchTermStr = Encoding.Default.GetBytes(tb_SearchTerm.Text).ToByteFlatString();

                    const int clustersRead = 1;
                    for (long i = 0; i < totalClusters; i += clustersRead)
                    {
                        var sectorData = await DiskManager.ReadSectorAsync(disk, offset, clustersRead * sectorsPerCluster, cancellationToken);
                        var dataStr = sectorData.ToByteFlatString();

                        var result = dataStr.Contains(searchTermStr);
                        if (result)
                        {
                            Console.WriteLine(@"{0}号线程完成，Offset:{1}", index, offset);
                            cts.Cancel();
                            return offset;
                        }

                        ProgressStates[index] += clustersRead; // 已完成的簇的数量

                        offset += clustersRead * sectorsPerCluster * DiskManager.SectorSize;

                        //await Task.Delay(100, cancellationToken);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(@"{0}号线程取消", index);
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"{0}号线程发生错误：{1}", index, ex.Message);
            }
            finally
            {
                Console.WriteLine(@"{0}号线程退出", index);
            }

            return 0;
        }

        private async Task UpdateProgress(long totalClusters, CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    lbl_Elapsed.Text = stopwatch.Elapsed.ToString(@"hh\:mm\:ss\.fff");
                    var clustersFinished = ProgressStates.Sum(ps => ps.Value);
                    lbl_Searching.Text = String.Format("{0} / {1}", clustersFinished, totalClusters);
                    pb_Search.Value = (int)(clustersFinished * pb_Search.Maximum / totalClusters);
                    await Task.Delay(50, cancellationToken);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(@"UpdateProgress取消");
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"UpdateProgress发生错误：{0}", ex.Message);
            }
            finally
            {
                Console.WriteLine(@"UpdateProgress退出");
            }
        }

        private async Task UpdateRemaining(long totalClusters, CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    var clustersFinished = ProgressStates.Sum(ps => ps.Value);
                    if (clustersFinished == 0)
                    {
                        await Task.Delay(1000, cancellationToken);
                        continue;
                    }

                    var remaining = TimeSpan.FromSeconds(stopwatch.Elapsed.TotalSeconds / clustersFinished * totalClusters - stopwatch.Elapsed.TotalSeconds);
                    lbl_Remaining.Text = String.Format("{0:%d} 天 {0:%h} 小时 {0:%m} 分钟", remaining);

                    if (queue != null)
                    {
                        lbl_QueueRemaining.Text = queue.Count.ToString();
                    }

                    await Task.Delay(1000, cancellationToken);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(@"UpdateRemaining取消");
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"UpdateRemaining发生错误：{0}", ex.Message);
            }
            finally
            {
                Console.WriteLine(@"UpdateRemaining退出");
            }
        }

        private async Task UpdateThreadsProgress(CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    lv_ThreadStatus.Items.Clear();
                    foreach (var item in ProgressStates)
                    {
                        lv_ThreadStatus.Items.Add(new ListViewItem(item.Value.ToString()));
                    }

                    await Task.Delay(100, cancellationToken);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(@"UpdateThreadsProgress取消");
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"UpdateThreadsProgress发生错误：{0}", ex.Message);
            }
            finally
            {
                Console.WriteLine(@"UpdateThreadsProgress退出");
            }
        }

        private void FullDiskSearchForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (cts != null)
                cts.Cancel();
        }

        private async void btn_SequentialSearch_Click(object sender, EventArgs e)
        {
            tb_ResultIndex.Clear();
            lbl_Searching.Text = String.Empty;
            pb_Search.Value = 0;
            btn_Search.Enabled = false;
            btn_SequentialSearch.Enabled = false;
            stopwatch.Reset();

            try
            {
                var totalClusters = totalSectors / sectorsPerCluster;

                using (cts = new CancellationTokenSource())
                {
                    queue = new ConcurrentQueue<Cluster>();

                    List<Task> readTasks = new List<Task>();
                    List<Task<long>> searchTasks = new List<Task<long>>();
                    const int readTaskCount = 1;
                    const int searchTaskCount = 4;
                    ReadingFinished = false;

                    stopwatch.Start();
                    var updateProgressTask = UpdateProgress(totalClusters, cts.Token);
                    var updateRemainingTask = UpdateRemaining(totalClusters, cts.Token);
                    var updateThreadsProgress = UpdateThreadsProgress(cts.Token);

                    for (int i = 0; i < readTaskCount; i++)
                    {
                        int index = i;

                        int clusterOffset;
                        if (int.TryParse(tb_ClusterOffset.Text, out clusterOffset))
                        {
                            clusterOffset = Convert.ToInt32(tb_ClusterOffset.Text);
                        }

                        var offset = (sectorsPrecedingPartition + clusterOffset * sectorsPerCluster + (totalClusters - clusterOffset) / readTaskCount * i * sectorsPerCluster) * DiskManager.SectorSize;
                        var readTask = Task.Run(() => SequentialReadAsync(index, offset, totalClusters / readTaskCount, cts.Token));
                        readTasks.Add(readTask);
                    }

                    for (int i = 0; i < searchTaskCount; i++)
                    {
                        int index = i;
                        var searchTask = Task.Run(() => SequentialSearchAsync(index, cts.Token));
                        searchTasks.Add(searchTask);
                    }

                    await Task.WhenAll(readTasks);
                    ReadingFinished = true;
                    await Task.WhenAll(searchTasks);

                    cts.Cancel();
                    await updateProgressTask;
                    await updateRemainingTask;
                    await updateThreadsProgress;

                    var taskHavingResult = searchTasks.Where(t => t.IsCompleted).FirstOrDefault(t => t.Result != 0);
                    if (taskHavingResult != null)
                    {
                        var clusterIndex = (taskHavingResult.Result / DiskManager.SectorSize - sectorsPrecedingPartition) / sectorsPerCluster;
                        tb_ResultIndex.Text = clusterIndex.ToString();
                        MessageBox.Show(@"查找已完成，发现结果", @"提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }
            }
            finally
            {
                btn_Search.Enabled = true;
                btn_SequentialSearch.Enabled = true;
                stopwatch.Stop();
                cts = null;
                queue = null;
                ProgressStates.Clear();
            }
        }

        private async Task SequentialReadAsync(int index, long offset, long totalClusters, CancellationToken cancellationToken)
        {
            try
            {
                using (var disk = GetStream())
                {
                    Console.WriteLine(@"{0}号Read线程启动，线程ID:{1}", index, Thread.CurrentThread.ManagedThreadId);

                    const int clustersRead = 16;

                    var clusterLength = sectorsPerCluster * DiskManager.SectorSize;
                    var bytesRead = clustersRead * clusterLength;

                    for (long i = 0; i < totalClusters; i += clustersRead)
                    {
                        var data = await DiskManager.ReadSectorAsync(disk, offset, clustersRead * sectorsPerCluster, cancellationToken);
                        for (int j = 0; j < clustersRead; j++)
                        {
                            byte[] clusterData = new byte[clusterLength];

                            Array.Copy(data, j * clusterLength, clusterData, 0, clusterLength);

                            var cluster = new Cluster
                            {
                                Offset = offset + j * clusterLength,
                                Data = clusterData
                            };

                            queue.Enqueue(cluster);
                        }

                        offset += bytesRead;

                        await Task.Delay(queue.Count / 10000, cancellationToken);
                    }
                }

                Console.WriteLine(@"{0}号Read线程任务完成", index);
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(@"{0}号Read线程取消", index);
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"{0}号Read线程发生错误：{1}", index, ex.Message);
            }
            finally
            {
                Console.WriteLine(@"{0}号Read线程退出", index);
            }
        }

        private async Task<long> SequentialSearchAsync(int index, CancellationToken cancellationToken)
        {
            try
            {
                Console.WriteLine(@"{0}号Search线程启动，线程ID:{1}", index, Thread.CurrentThread.ManagedThreadId);

                ProgressStates[index] = 0;

                var searchTermStr = Encoding.Default.GetBytes(tb_SearchTerm.Text).ToByteFlatString();

                while (true)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    Cluster cluster;
                    if (queue.TryDequeue(out cluster))
                    {
                        var dataStr = cluster.Data.ToByteFlatString();
                        var result = dataStr.Contains(searchTermStr);
                        if (result)
                        {
                            Console.WriteLine(@"{0}号Search线程完成，Offset:{1}", index, cluster.Offset);
                            cts.Cancel();
                            return cluster.Offset;
                        }

                        ProgressStates[index]++; // 已完成的簇的数量
                    }
                    else
                    {
                        if (ReadingFinished) break;

                        Console.WriteLine(@"{0}号Search线程正在等待", index);
                        await Task.Delay(100, cancellationToken);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine(@"{0}号Search线程取消", index);
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"{0}号Search线程发生错误：{1}", index, ex.Message);
            }
            finally
            {
                Console.WriteLine(@"{0}号Search线程退出", index);
            }

            return 0;
        }

        private void btn_Export_Click(object sender, EventArgs e)
        {
            if (saveOutputFileDialog.ShowDialog() != DialogResult.OK) return;

            using (var stream = saveOutputFileDialog.OpenFile())
            using (var disk = GetStream())
            {
                var clusterIndex = long.Parse(tb_ClusterIndex.Text);
                var clusterCount = int.Parse(tb_ClusterCount.Text);
                var offset = (sectorsPrecedingPartition + clusterIndex * sectorsPerCluster) * DiskManager.SectorSize;

                var buffer = DiskManager.ReadSector(disk, offset, clusterCount * sectorsPerCluster);
                stream.Write(buffer, 0, buffer.Length);
            }
        }

        private Stream GetStream()
        {
            return DiskManager.GetStream();
        }
    }

    public class Cluster
    {
        public long Offset { get; set; }

        public byte[] Data { get; set; }
    }
}
